# -*- coding: UTF-8 -*-
import json
import tkinter as tk
from tkinter import ttk
from threading import Lock
from collections import OrderedDict
from Code.Utils.sc_func import Common, Timer
from Code.View.Datamation.sc_provide import view_gate
from Code.View.sc_global import Global
from Code.View.sc_base import TopWinBase
from Code.View.sc_module import TitleFrame, WidgetTip, LabelEntry, ConfirmEntry, ScrollFrame, ProgressBar, MenuBar

language_data = view_gate.query_language_data


class Human:

    @staticmethod
    def image(key):
        return view_gate.query_photo_image_data('FL_%s' % key)

    @staticmethod
    def size(s):
        if s < 1024:
            return '%sB' % s
        elif 1024 <= s < 1048576:
            return '%.1fKB' % (s / 1024.0)
        elif 1048576 <= s < 1073741824:
            return '%.2fMB' % (s / 1048576.0)
        return '%.2fGB' % (s / 1073741824.0)


class FileTreeTop(TopWinBase):
    """ sftp目录树界面 """
    def init(self):
        self.shell = 'python {}/sftp_interface.py'.format(view_gate.query_server_dir_data())
        self.width = Global.G_APP_WIDTH
        self.height = Global.G_APP_HEIGHT
        self.auto_switch = False
        self.auto_period = 60         # 自动刷新linux目录的周期 #
        self.linux_notebook = None    # linux窗中各IP页签 #
        self.linux_instance = {}      # 保存linux界面控件实例 #
        self.windows_instance = []    # 保存windows界面控件实例 #
        self.transfer_instance = {}   # 保存传输界面控件实例 #
        self.linux_sub_dirs = {}      # 保存linux所有IP的当前目录下所有子目录(首个为当前目录) #
        self.windows_curr_dir = None  # windows界面当前显示的目录 #
        self.update_locks = [Lock(), Lock()]    # windows/linux界面更新锁 #

    def pack(self):
        self.init()
        self.master.title('Bingo-SFTP')
        self.resize(self.width, self.height)
        #self.topping(True)

        master = TitleFrame(self.master, width=self.width, height=self.height).master()
        windows_fm, transfer_fm = self.pack_main(master)        # 主框 #
        self.pack_windows(windows_fm)              # windows框 #
        for ip in view_gate.query_login_ip_data():
            self.append_linux_instance(ip)         # linux框 #
        self.pack_transfer(transfer_fm)            # 底部传输框 #
        self.linux_refresh_start(path=r'/')        # 默认显示linux / 目录 #
        if self.auto_switch:                       # 开启定时自动刷新 #
            Timer(self.auto_refresh_timer, self.auto_period).start()

    def pack_main(self, master):
        linux_w, linux_h = self.get_width_height('linux')
        win_w, win_h = self.get_width_height('windows')
        trans_w, trans_h = self.get_width_height('transfer')

        paned_main = ttk.Panedwindow(master, orient=tk.VERTICAL, width=self.width, height=self.height)
        win_style = {'master': paned_main, 'width': self.width}
        trees_win = tk.Frame(height=self.height, **win_style)
        trans_win = tk.Frame(height=self.height / 5, **win_style)
        trees_win.pack(fill='both')
        trees_win.pack_propagate(0)
        trans_win.pack(fill='both')
        trans_win.pack_propagate(0)
        paned_main.add(trees_win, weight=3)
        paned_main.add(trans_win, weight=1)
        paned_main.pack(fill='both')

        linux_win = tk.Frame(trees_win, width=linux_w, height=linux_h)
        windows_win = tk.Frame(trees_win, width=win_w, height=win_h)
        windows_win.pack(side='left', fill='both')
        linux_win.pack(side='left', fill='both')
        linux_fm = TitleFrame(linux_win, width=linux_w, height=linux_h, title='Linux Server', center=True).master()
        windows_fm = TitleFrame(windows_win, width=win_w, height=win_h, title='Windows Local', center=True).master()
        transfer_fm = TitleFrame(trans_win, width=trans_w, height=trans_h).master()    #, title='', center=True
        self.linux_notebook = ttk.Notebook(linux_fm, style="App.TNotebook")
        self.linux_notebook.pack()
        return windows_fm, transfer_fm

    def pack_linux(self, ip, master):
        def input_enter(event=None):
            root = input_en.get()
            if root and root.startswith(r'/'):
                self.linux_refresh_start(ip, root)
            else:
                WidgetTip.error(language_data('INVALID_PARAMETERS'))

        width, height = self.get_width_height('linux')
        _oper_fm = TitleFrame(master, width=width, height=55).master()
        _tree_fm = TitleFrame(master, width=width, height=height - 55).master()
        input_en = LabelEntry(_oper_fm, language_data('DIRECTORY'), r'/', row=0, lab_width=10, en_width=120)
        show_en = LabelEntry(_oper_fm, language_data('CURRENT'), r'/', row=1, lab_width=10, en_width=120)
        input_en.entry.bind('<Return>', input_enter)
        show_en.entry['state'] = 'disabled'
        # tree #
        columns = ('size', 'time', 'attr', 'owner', 'type')
        tree = ttk.Treeview(_tree_fm, columns=columns, height=50)
        tree.column("#0", anchor="w", width=212)         # 设置第0列信息 #
        tree.heading("#0", text=language_data('NAME'))
        tree.column('size', width=90, anchor='e')
        tree.column('time', width=150, anchor='w')
        tree.column('attr', width=70, anchor='w')
        tree.column('owner', width=80, anchor='w')
        tree.column('type', width=70, anchor='w')
        tree.heading('size', text=language_data('SIZE'))
        tree.heading('time', text=language_data('MODIFY', 'TIME'))
        tree.heading('attr', text=language_data('ATTRIBUTE'))
        tree.heading('owner', text=language_data('OWNER'))
        tree.heading('type', text=language_data('TYPE'))
        tree.pack(side='left', fill='both')
        # treeview 绑定滑块 #
        scr2 = ttk.Scrollbar(_tree_fm, orient='vertical', command=tree.yview)
        scr2.pack(side='left', fill='y')
        tree.configure(yscrollcommand=scr2.set)
        tree.bind('<<TreeviewSelect>>', lambda event: self.linux_one_click(ip))
        tree.bind('<Double-Button-1>', lambda event: self.linux_double_click(ip))
        tree.bind('<Return>', lambda event: self.linux_double_click(ip))
        tree.bind('<Button-3>', lambda event: self.linux_right_click(ip, event))
        tree.bind('<BackSpace>', lambda event: self.linux_click_back(ip))
        self.linux_instance[ip] = (input_en, show_en, tree)

    def pack_windows(self, master):
        def input_enter(event=None):
            root = input_en.get()
            if root and Common.is_dir(root):
                self.windows_refresh_start(root)
            else:
                WidgetTip.error(language_data('DIRECTORY', 'NOT_EXIST'))

        desktop_path = Common.get_windows_desktop_path()
        disks = Common.get_windows_disks()
        width, height = self.get_width_height('windows')
        _oper_fm = tk.Frame(master)
        _tree_fm = tk.Frame(master)
        _oper_fm.pack()
        _tree_fm.pack()
        input_en = LabelEntry(_oper_fm, language_data('DIRECTORY'), desktop_path, row=0, lab_width=8, en_width=120)
        show_en = LabelEntry(_oper_fm, language_data('CURRENT'), desktop_path, row=1, lab_width=8, en_width=120)
        input_en.entry.bind('<Return>', input_enter)
        show_en.entry['state'] = 'disabled'

        paned_tree = ttk.Panedwindow(_tree_fm, orient=tk.HORIZONTAL, width=width, height=height)
        list_fm = tk.Frame(paned_tree, width=int(width / 2), height=height)
        tree_fm = tk.Frame(paned_tree, width=int(width / 2), height=height)
        list_fm.pack(side='left')
        tree_fm.pack(side='left')
        paned_tree.add(list_fm, weight=2)
        paned_tree.add(tree_fm, weight=2)
        paned_tree.pack(fill='both')
        # lists #
        lists = ttk.Treeview(list_fm, height=50, show='tree', selectmode='browse')
        lists.column("#0", anchor="w", width=300)    # 设置第0列信息 #
        lists.heading("#0", text=language_data('NAME'))
        lists.pack(side='left', fill='both')
        lists.insert('', 'end', 'PC', text=language_data('MY_PC'), image=Human.image('PC'))
        lists.item('PC', open=True)
        for disk in disks:
            lists.insert('PC', 'end', 'PC/%s'%disk.strip('/'), text=disk, image=Human.image('DISK'))
        #scr1 = ttk.Scrollbar(list_fm, orient='vertical', command=lists.yview)
        #scr1.pack(side='left', fill='y')
        #lists.configure(yscrollcommand=scr1.set)
        lists.bind('<Button-1>', lambda event: self.windows_click_path(event))
        # tree #
        columns = ('size', 'type')
        tree = ttk.Treeview(tree_fm, columns=columns, height=50)
        tree.column("#0", anchor="w", width=300)       # 设置第0列信息 #
        tree.heading("#0", text=language_data('NAME'))
        tree.column('size', width=88, anchor='e')
        tree.column('type', width=80, anchor='w')
        tree.heading('size', text=language_data('SIZE'))
        tree.heading('type', text=language_data('TYPE'))
        tree.pack(side='left', fill='both')
        # treeview 绑定滑块 #
        scroll = ttk.Scrollbar(tree_fm, orient='vertical', command=tree.yview)
        scroll.pack(side='left', fill='y')
        tree.configure(yscrollcommand=scroll.set)
        tree.bind('<<TreeviewSelect>>', self.windows_one_click)
        tree.bind('<Double-Button-1>', self.windows_double_click)
        tree.bind('<Return>', self.windows_double_click)
        tree.bind('<Button-3>', self.windows_right_click)
        tree.bind('<BackSpace>', self.windows_click_back)
        self.windows_instance = (input_en, show_en, lists, tree)
        self.windows_refresh_start(desktop_path)

    def pack_transfer(self, master):
        width, height = self.get_width_height('transfer')
        head = TitleFrame(master, width=width, height=20).master()
        body = TitleFrame(master, width=width, height=height).master()
        tk.Label(head, text=language_data('NAME'), width=37, anchor='w').pack(side='left', padx=20)
        tk.Label(head, text=language_data('DIRECTION'), width=8, anchor='w').pack(side='left')
        tk.Label(head, text=language_data('SERVER'), width=18, anchor='w').pack(side='left')
        tk.Label(head, text=language_data('STATUS'), width=20, anchor='w').pack(side='left')
        tk.Label(head, text=language_data('PROGRESS'), width=24).pack(side='left')
        tk.Label(head, text=language_data('SIZE'), width=18, anchor='w').pack(side='left')
        tk.Label(head, text=language_data('SOURCE'), width=50, anchor='w').pack(side='left')
        tk.Label(head, text=language_data('DESTINATION'), width=50, anchor='w').pack(side='left')
        self.transfer_instance['master'] = ScrollFrame(body).master()

    def append_trans_instance(self, data):
        def destroy(event=None):
            sub_master.destroy()
            self.transfer_instance[key] = None
            del self.transfer_instance[key]

        def update(_data):
            _ret, _prog, _size = _data
            _used_t = '[Used:{}s]'.format(int(Common.time() - start_t))
            if _ret:
                _state = 'Success' if _prog == 100 else 'Ongoing'
                _state = '{} {}'.format(_state, _used_t)
                lab4['text'] = _state             # 更新上传状态和耗时 #
                lab5['text'] = _size              # 更新上传大小信息 #
            else:
                lab4['text'] = '{} {}'.format(language_data('FAILED'), _used_t)
            progress.update(_prog, _ret)          # 更新上传进度 #

        name, updown, ip, src, dest, types = data
        start_t = Common.time()
        image = Human.image(types)
        if updown == language_data('DOWNLOAD'):
            src_str = src
        else:
            src_str = Common.dirname(src)

        sub_master = tk.Frame(self.transfer_instance['master'], width=self.width, height=20)
        sub_master.pack()
        sub_master.pack_propagate(0)
        tk.Button(sub_master, text='X', bd=0, command=destroy).pack(side='left')
        lab1 = tk.Label(sub_master, text=name, image=image, width=280, compound='left', anchor='w')
        lab2 = tk.Label(sub_master, text=updown, width=8, anchor='w')
        lab3 = tk.Label(sub_master, text=ip, width=16, anchor='w')
        lab4 = tk.Label(sub_master, text='Waiting', width=20, anchor='w')
        progress_fm = tk.Frame(sub_master, width=50, height=18)
        lab5 = tk.Label(sub_master, text='Counting', width=18, anchor='w')
        lab6 = tk.Label(sub_master, text=src_str, width=50, anchor='w')
        lab7 = tk.Label(sub_master, text=dest, width=50, anchor='w')

        lab1.pack(side='left')
        lab2.pack(side='left')
        lab3.pack(side='left')
        lab4.pack(side='left')
        progress_fm.pack(side='left')
        progress_fm.pack_propagate(0)
        lab5.pack(side='left')
        lab6.pack(side='left')
        lab7.pack(side='left')

        progress = ProgressBar(progress_fm)

        key = '{}|{}|{}'.format(ip, src, dest)
        self.transfer_instance[key] = update

    def fill_windows_lists(self, root, dirs):
        lists = self.windows_instance[2]
        image = Human.image('DIR')
        # 先补充根 #
        top = 'PC'
        root = root.strip('/')
        for name in root.split('/'):
            path = Common.normal_path('{}/{}'.format(top, name))
            if path not in lists.get_children(top):
                lists.insert(top, 'end', path, text=name, image=image)
            top = path
        # 再补充子目录 #
        root = 'PC/%s' % root
        child = lists.get_children(root)
        for d in dirs:
            path = Common.normal_path('{}/{}'.format(root, d))
            if path not in child:
                lists.insert(root, 'end', path, text=d, image=image)
        lists.item(root, open=True)
        lists.focus(root)
        lists.see(root)

    def fill_windows_tree(self, dir):
        disks = Common.get_windows_disks()
        root, dirs, files = Common.list_dir(dir)
        root = Common.normal_path(root)
        # 补充lists #
        self.fill_windows_lists(root, dirs)

        self.windows_instance[0].set(root)
        self.windows_curr_dir = dir

        tree = self.windows_instance[3]
        # 先删除所有子树 #
        [tree.delete(i) for i in tree.get_children()]

        # windows桌面时, 最先显示所有盘符, 否则最先显示 .. #
        if dir == Common.get_windows_desktop_path():
            for path in disks:
                tree.insert('', 'end', path, text=path, image=Human.image('DISK'), values=('-', 'DISK'))
        else:
            tree.insert('', 'end', '..', text='..', image=Human.image('UPPER'))

        for path in dirs:
            # 过滤windows隐藏文件夹 #
            if path.startswith('$'):
                continue
            tree.insert('', 'end', path, text=path, image=Human.image('DIR'), values=('-', 'DIR'))

        for path in files:
            # 过滤windows隐藏文件和链接文件 #
            if path.startswith('~$') or path.endswith('.lnk'):
                continue
            size = Human.size(Common.file_size('{}/{}'.format(root, path)))
            tree.insert('', 'end', path, text=path, image=Human.image('FILE'), values=(size, 'FILE'))

    def fill_linux_tree(self, ip, values):
        if ip not in self.linux_instance:
            self.append_linux_instance(ip)

        real_path = values[0]     # 首行为当前的真实目录 #
        self.linux_instance[ip][0].set(real_path)
        self.linux_sub_dirs[ip] = [real_path, '..']

        tree = self.linux_instance[ip][2]
        # 先删除所有子树 #
        [tree.delete(i) for i in tree.get_children()]
        # 非 / 根目录时显示 .. 上层目录按钮 #
        if real_path != '/':
            tree.insert('', 'end', '..', text='..', image=Human.image('UPPER'))

        # 重新插入子树 #
        for value in values[1:]:
            path = value[0]
            if value[-1] in ('DIR', 'LINK-DIR'):
                self.linux_sub_dirs[ip].append(path)
            tree.insert('', 'end', path, text=path, image=Human.image(value[-1]), values=value[1:])

    def linux_one_click(self, ip):
        root = self.linux_sub_dirs[ip][0]
        path = self.linux_instance[ip][2].focus()
        click_path = '{}/{}'.format(root, path).replace('//', '/')
        self.linux_instance[ip][1].set(click_path)

    def linux_click_back(self, ip):
        if self.update_locks[1].locked():
            return
        root = self.linux_sub_dirs[ip][0]
        if root == '/':
            return
        path = '{}/..'.format(root).replace('//', '/')
        self.linux_refresh_start(ip, path)

    def linux_double_click(self, ip):
        if self.update_locks[1].locked():  # 如果锁住, 说明正在更新tree布局, 则暂停处理双击事件 #
            return
        root = self.linux_sub_dirs[ip][0]
        path = self.linux_instance[ip][2].focus()
        if not path: return
        click_path = '{}/{}'.format(root, path).replace('//', '/')
        if path in self.linux_sub_dirs[ip][1:]:
            self.linux_refresh_start(ip, click_path)
        else:
            src = {ip: [click_path]}
            self.linux_download_start(src)

    def linux_right_click(self, ip, event):
        def choose(key):
            if key == 'REFRESH':
                self.linux_refresh_start()
            elif key == 'SELECT_ALL':
                [tree.selection_add(i) for i in tree.get_children()]
            elif key == 'DOWNLOAD':
                paths = ['{}/{}'.format(root, s) for s in select if s != '..']
                src = {ip: paths}
                self.linux_download_start(src)
            elif key == 'DOWNLOAD_FULL':
                src = {}
                for _ip, _inst in self.linux_instance.items():
                    rs = self.linux_sub_dirs[_ip][0]
                    ss = _inst[2].selection()
                    src[_ip] = ['{}/{}'.format(rs, s) for s in ss if s != '..']
                self.linux_download_start(src)
            elif key == 'RENAME':
                def rename(new, vars=None):
                    tasks = {ip: 'cd {} && mv -f {} {}'.format(root, old, new)}
                    data = (tasks, 'BOOL', True)
                    self.register_single_start(data)
                old = tree.focus()
                if old == '..':
                    return
                ConfirmEntry(rename, language_data('NAME'), old)
            elif key == 'DELETE':
                if not WidgetTip.ask('%s ?' % language_data('CONFIRM', 'DELETE')):
                    return
                paths = ' '.join(['{}/{}'.format(root, s) for s in select if s != '..'])
                tasks = {ip: 'rm -rf {}'.format(paths)}
                data = (tasks, 'BOOL', True)
                self.register_single_start(data)
            elif key == 'CHANGE_ATTR':
                def change(attr, vars):
                    tasks = {ip: 'cd {} && chmod {} {}'.format(root, attr, p)}
                    data = (tasks, 'BOOL', vars[0])
                    self.register_single_start(data)
                p = tree.focus()
                if p == '..':
                    return
                ConfirmEntry(change, language_data('ATTRIBUTE'), '750',
                             [language_data('SWITCH_ROOT_USER')])
            elif key == 'NEW_FILE':
                def touch(name, vars):
                    tasks = {ip: 'cd {} && touch {}'.format(root, name)}
                    data = (tasks, 'BOOL', vars[0])
                    self.register_single_start(data)
                ConfirmEntry(touch, language_data('FILE', 'NAME'), '',
                             [language_data('SWITCH_ROOT_USER')])
            elif key == 'NEW_DIR':
                def mkdir(name, vars):
                    tasks = {ip: 'cd {} && mkdir {}'.format(root, name)}
                    data = (tasks, 'BOOL', vars[0])
                    self.register_single_start(data)
                ConfirmEntry(mkdir, language_data('DIRECTORY', 'NAME'), '',
                             [language_data('SWITCH_ROOT_USER')])

        if self.update_locks[1].locked():
            return
        root = self.linux_sub_dirs[ip][0]
        tree = self.linux_instance[ip][2]
        select = tree.selection()
        _down = language_data('DOWNLOAD')
        _down_all = language_data('DOWNLOAD', 'ALL_SERVER_SELECTION')
        _refresh = language_data('REFRESH')
        _sect_all = language_data('SELECT_ALL')
        _new = language_data('NEW')
        _new_f = language_data('NEW', 'FILE')
        _new_d = language_data('NEW', 'DIRECTORY')
        _del = language_data('DELETE')
        _rename = language_data('RENAME')
        _chg_attr = language_data('CHANGE', 'ATTRIBUTE')

        if len(select) > 1:
            items = [(_down, 'DOWNLOAD'), (_down_all, 'DOWNLOAD_FULL'), '-', (_refresh, 'REFRESH'),
                     (_sect_all, 'SELECT_ALL'), '-', (_new, [(_new_f, 'NEW_FILE'), (_new_d, 'NEW_DIR')]),
                     '-', (_del, 'DELETE')]
        elif len(select) == 1:
            items = [(_down, 'DOWNLOAD'), (_down_all, 'DOWNLOAD_FULL'), '-', (_refresh, 'REFRESH'),
                     (_sect_all, 'SELECT_ALL'), '-', (_rename, 'RENAME'), (_chg_attr, 'CHANGE_ATTR'), '-',
                     (_new, [(_new_f, 'NEW_FILE'), (_new_d, 'NEW_DIR')]), '-', (_del, 'DELETE')]
        else:
            items = [(_refresh, 'REFRESH'), (_sect_all, 'SELECT_ALL'), '-',
                     (_new, [(_new_f, 'NEW_FILE'), (_new_d, 'NEW_DIR')])]
        MenuBar(tree, items, choose, True, event)

    def linux_download_start(self, src_map):
        # 下载是把所有选中项(在同一个目录下)一起打包下载, 因此src只要上层目录一级 #
        dest = self.windows_curr_dir
        srcs = {}
        for ip, paths in src_map.items():
            if not paths:
                continue
            srcs[ip] = paths
            src = Common.dirname(paths[0])
            if len(paths) == 1:
                name = Common.basename(paths[0])
                types = 'FILE'
            else:
                name = '{}/..'.format(Common.basename(src))
                types = 'DIR'
            self.append_trans_instance((name, language_data('DOWNLOAD'), ip, src, dest, types))

        self.register_single_download((srcs, dest))

    def windows_one_click(self, event=None):
        self.windows_instance[1].set(self.windows_instance[3].focus())

    def windows_click_path(self, event):
        if self.update_locks[0].locked():
            return
        path = self.windows_instance[2].identify_row(event.y)
        path = '{}/'.format(path.split('PC/')[1])
        self.windows_refresh_start(path)

    def windows_click_back(self, event=None):
        if self.update_locks[0].locked():
            return
        root = self.windows_curr_dir
        desktop = Common.get_windows_desktop_path()
        if root == desktop:
            return
        if root in Common.get_windows_disks():
            path = desktop
        else:
            path = '{}/..'.format(root)
        self.windows_refresh_start(path)

    def windows_double_click(self, event=None):
        if self.update_locks[0].locked():
            return
        root = self.windows_curr_dir
        path = self.windows_instance[3].focus()
        disks = Common.get_windows_disks()
        # 设置path路径 #
        if path == '..' and root in disks:   # 如果是盘符根目录点击.., 则回到桌面 #
            path = Common.get_windows_desktop_path()
        elif path not in disks:
            path = '{}/{}'.format(root, path)
        if Common.is_dir(path):       # path 为目录, 则进入该目录 #
            self.windows_refresh_start(path)
        elif Common.is_file(path):    # path 为文件, 则上传该文件至当前选中服务器 #
            self.windows_upload_start([path])

    def windows_right_click(self, event):
        def choose(key):
            if key == 'REFRESH':
                self.windows_refresh_start(root)
            elif key == 'SELECT_ALL':
                [tree.selection_add(i) for i in tree.get_children()]
            elif key in ['UPLOAD', 'UPLOAD_FULL']:
                disks, src = Common.get_windows_disks(), []
                for s in select:
                    if (s in disks) or (s == '..'):
                        continue
                    src.append('{}/{}'.format(root, s))
                self.windows_upload_start(src, True if key == 'UPLOAD_FULL' else False)
            elif key == 'RENAME':
                def rename(new, val=None):
                    Common.rename('{}/{}'.format(root, old), '{}/{}'.format(root, new))
                    choose('REFRESH')
                old = tree.focus()
                if old == '..':
                    return
                ConfirmEntry(rename, language_data('FILE', 'NAME'), old)
            elif key == 'DELETE':
                if not WidgetTip.ask('%s ?' % language_data('CONFIRM', 'DELETE')):
                    return
                [Common.remove("{}/{}".format(root, s)) for s in select
                 if s != '..' and Common.is_file("{}/{}".format(root, s))]
                [Common.rm_dir("{}/{}".format(root, s)) for s in select
                 if s != '..' and Common.is_dir("{}/{}".format(root, s))]
                choose('REFRESH')
            elif key == 'NEW_FILE':
                def touch(name, val=None):
                    Common.write_to_file('{}/{}'.format(root, name), '')
                    choose('REFRESH')
                ConfirmEntry(touch, language_data('FILE', 'NAME'))
            elif key == 'NEW_DIR':
                def mkdir(name, val=None):
                    Common.mkdir('{}/{}'.format(root, name))
                    choose('REFRESH')
                ConfirmEntry(mkdir, language_data('DIRECTORY', 'NAME'))

        root = self.windows_curr_dir
        tree = self.windows_instance[3]
        select = tree.selection()
        _upload = language_data('UPLOAD')
        _upload_all = language_data('UPLOAD', 'TO_ALL_SERVER')
        _refresh = language_data('REFRESH')
        _sect_all = language_data('SELECT_ALL')
        _new = language_data('NEW')
        _new_f = language_data('NEW', 'FILE')
        _new_d = language_data('NEW', 'DIRECTORY')
        _del = language_data('DELETE')
        _rename = language_data('RENAME')
        if len(select) > 1:
            items = [(_upload, 'UPLOAD'), (_upload_all, 'UPLOAD_FULL'), '-', (_refresh, 'REFRESH'),
                     (_sect_all, 'SELECT_ALL'), '-', (_new, [(_new_f, 'NEW_FILE'), (_new_d, 'NEW_DIR')]),
                     '-', (_del, 'DELETE')]
        elif len(select) == 1:
            items = [(_upload, 'UPLOAD'), (_upload_all, 'UPLOAD_FULL'), '-', (_refresh, 'REFRESH'),
                     (_sect_all, 'SELECT_ALL'), '-', (_new, [(_new_f, 'NEW_FILE'), (_new_d, 'NEW_DIR')]),
                     '-', (_rename, 'RENAME'), (_del, 'DELETE')]
        else:
            items = [(_refresh, 'REFRESH'), (_sect_all, 'SELECT_ALL'), '-',
                     (_new, [(_new_f, 'NEW_FILE'), (_new_d, 'NEW_DIR')])]
        MenuBar(tree, items, choose, True, event)

    def windows_upload_start(self, src_list, full=False):
        if full:
            ips = list(self.linux_instance.keys())
        else:
            ips = [self.linux_notebook.tab(self.linux_notebook.select(), "text")]
        dest_map = {ip: self.linux_sub_dirs[ip][0] for ip in ips}

        for src in src_list:
            for ip, dest in dest_map.items():
                types = 'FILE' if Common.is_file(src) else 'DIR'
                self.append_trans_instance((Common.basename(src), language_data('UPLOAD'), ip, src, dest, types))
        self.register_single_upload((src_list, dest_map))

    def get_width_height(self, key='max'):
        return {
            'linux': (int(self.width / 2), self.height),
            'windows': (int(self.width / 2), self.height),
            'transfer': (self.width, int(self.height / 5))
        }[key]

    def append_linux_instance(self, ip):
        """ 新增一个IP页 """
        fm = tk.Frame(self.linux_notebook)
        fm.pack(fill='both')
        self.linux_notebook.add(fm, text=ip)
        self.pack_linux(ip, fm)

    def auto_refresh_timer(self, args=None):
        self.linux_refresh_start()
        self.windows_refresh_start()

    def windows_refresh_start(self, path=None):
        if not path:
            path = self.windows_curr_dir
        if not Common.is_dir(path):
            WidgetTip.error('{} {}'.format(path, language_data('DIRECTORY', 'NOT_EXIST')))
            return
        self.update_locks[0].acquire()
        try:
            self.fill_windows_tree(Common.normal_path(path))
        finally:
            self.update_locks[0].release()

    def linux_refresh_start(self, ip=None, path=None):
        ''' 支持:
        1. 单IP刷新指定目录     (ip, path)
        2. 单IP刷新当前目录     (ip, None)
        3. 所有IP刷新指定目录     (None, path)
        3. 所有IP刷新各自当前目录  (None, None)
        '''
        ips = [ip] if ip else list(self.linux_instance.keys())
        if path:
            tasks = {i: '{} {}'.format(self.shell, path) for i in ips}
        else:
            tasks = {i: '{} {}'.format(self.shell, self.linux_sub_dirs[i][0]) for i in ips}
        data = (tasks, 'INFO', True)
        self.register_single_start(data)

    def register_single_start(self, data):
        view_gate.register_single_start(self)
        view_gate.single_start_data.set_data(data)

    def register_single_upload(self, data):
        view_gate.register_single_start(self)
        view_gate.single_upload_data.set_data(data)

    def register_single_download(self, data):
        view_gate.register_single_start(self)
        view_gate.single_download_data.set_data(data)

    def single_start_result(self, data):
        if not self.master: return
        ip, flag, info, err = data
        if err:
            WidgetTip.error('{}\n{}.'.format(ip, language_data('TASK', 'FAILED')))
            return
        func, arg = print, (0,)
        if flag == 'BOOL':
            func = self.linux_refresh_start
            arg = (ip,)
        elif flag == 'INFO':
            if info == 'null':
                WidgetTip.error('{}\n{}.'.format(ip, language_data('DIRECTORY', 'NOT_EXIST')))
                return
            func = self.fill_linux_tree
            arg = (ip, json.loads(info, object_pairs_hook=OrderedDict))

        self.update_locks[1].acquire()
        try:
            func(*arg)
        finally:
            self.update_locks[1].release()

    def single_upload_result(self, data):
        for ip, result in data.items():
            ret_type, src, dest = result[:3]
            key = '{}|{}|{}'.format(ip, src, dest)
            update_func = self.transfer_instance[key]
            if ret_type == 'PROG':
                progress, curr, total = result[3:6]
                size = '{}/{}'.format(Human.size(curr), Human.size(total))
                update_func((True, progress, size))
            elif ret_type == 'END':
                # 上传完之后刷新一下linux tree #
                self.linux_refresh_start(ip)
                ret, err = result[3:5]
                if not ret:
                    update_func((False, 0, ''))
                    WidgetTip.error("{}\n{}: {}".format(ip, language_data('UPLOAD', 'FAILED'), err))

    def single_download_result(self, data):
        for ip, result in data.items():
            ret_type, src_list, dest = result[:3]
            key = '{}|{}|{}'.format(ip, Common.dirname(src_list[0]), dest)
            update_func = self.transfer_instance[key]
            if ret_type == 'PROG':
                progress, curr, total = result[3:6]
                size_str = '{}/{}'.format(Human.size(curr), Human.size(total))
                update_func((True, progress, size_str))
            elif ret_type == 'END':
                # 下载完之后刷新一下windows tree #
                self.windows_refresh_start()
                ret, err = result[3:5]
                if not ret:
                    update_func((False, 0, ''))
                    WidgetTip.error("{}\n{}: {}".format(ip, language_data('DOWNLOAD', 'FAILED'), err))

